package dcutils.rpg;

// Import Java JDK Classes
import static java.lang.Math.*;

/**
 * An default implementation of the Experienceable interface which can be used by RPG actors.<br/>
 * An instance of this class is meant to be contained alongside or within another object whose experience/levels you want to gauge.<br/>
 * @author dca
 */
public class DefaultExperienceable implements Experienceable {
   // Level and XP values

   /** The current level.<br/> */
   private int level;

   /** The current amount of experience points.<br/> */
   private long xp;

   // values for determining experience curve values

   /** Used to determine a factor by which to determine (xp) within levels.<br/> */
   private double xpBaseConst = 3.5;

   /** Used to determine a factor by which to determine (xp) within levels.<br/> */
   private double xpAugConst = 0.075;

   /**
    * The constructor initializes the level to the value of one, and the amount of xp to the value of zero.<br/>
    */
   public DefaultExperienceable() {
      level = 1;
      xp = 0;
   } // END constructor

   /**
    * The constructor initializes the level to the supplied level, and the amount of xp to whichever xp it would have taken to reach the supplied level.<br/>
    * @param level The supplied level.<br/>
    */
   public DefaultExperienceable(int level) {
      if(1 > level) throw new IllegalArgumentException("Level cannot be less than 1.  Level was: " + level);
      this.level = level;
      xp = xpToReach(level);
   } // END constructor

   /**
    * The getter for the level.<br/>
    * @return integer The level.<br/>
    */
   @Override
   public int level() {
      return level;
   } // END level

   /**
    * The getter for the experience points (xp)<br/>
    * @return long The experience points (xp)<br/>
    */
   @Override
   public long xp() {
      return xp;
   } // END xp

   /**
    * Returns the amount of experience points (xp) needed to reach the next level;<br/>
    * based on the current level and current experience points (xp)<br/>
    * @return long The amount of experience points (xp) needed.<br/>
    */
   @Override
   public long xpToNextLevel() {
      return xpToReach(level + 1) - xp;
   } // END xpToNextLevel

   /**
    * Adds the supplied amount of experience points (xp) to the current experience points (xp);<br/>
    * and returns whether or not it was enough to advance the level.<br/>
    * @param xp The supplied experience points (xp)<br/>
    * @return True if the supplied experience points (xp) was enough to advance to the next level; false otherwise.<br/>
    */
   @Override
   public boolean increaseXp(long xp) {
      this.xp += xp;

      final boolean levelUp = (xpToNextLevel() < 0);

      while(xpToNextLevel() < 0) ++level;

      return levelUp;
   } // END increaseXp

   /**
    * Returns a string, formatted to a comma-delimited list of the current level, experience points (xp), and xp to reach next level.<br/>
    * @return String A formatted string with information about the current level, exp and xp needed to reach the next level.<br/>
    */
   @Override
   public String toString() {
      return String.format("Level: %d, Exp: %d, Next: %d", level, xp, xpToNextLevel());
   } // END toString

   /**
    * Decides if an object is equal to this DefaultExperienceable.<br/>
    * In order for an object to be equal to this DefaultExperienceable:<br/>
    * the object must also be a DefaultExperienceable, must not be null, and the level and xp must match this DefaultExperienceable&apos;s level and xp.<br/>
    * @param obj The item to check for value equality.<br/>
    * @return True or false<br/>
    */
   @Override
   public boolean equals(Object obj) {
      if(null == obj) {
         return false;
      } else if(obj instanceof DefaultExperienceable) {
         DefaultExperienceable de = (DefaultExperienceable)obj;
         return this.level() == de.level() &&
         this.xp() == de.xp();
      } else {
         return false;
      } // END if/else
   } // END equals

   /**
    * Creates a hash code for this DefaultExperienceable.<br/>
    * The hash code uses prime numbers and the level and xp value to create number.<br/>
    * @return integer The hash code.<br/>
    */
   @Override
   public int hashCode() {
      int hashCode = 101;
      hashCode = (11 * hashCode) + this.level();
      hashCode = (11 * hashCode) + Long.valueOf(this.xp()).intValue();
      return hashCode;
   } // END hashCode

   /////////////////////////////////////////////////////////
   // helper methods for determining experience curve values
   /////////////////////////////////////////////////////////

   /**
    * Returns a value to be applied to a greater expression to determine xp-to-level values.<br/>
    * @param level The level for which to generate the value.<br/>
    * @return double The value.<br/>
    */
   private double getXpConst(int level) {
      return xpBaseConst + (xpAugConst * floor(level));
   } // END getXpConstant

   /**
    * Returns the amount of total experience points (xp) that is needed to reach the supplied level.<br/>
    * @param level The supplied level.<br/>
    * @return long The amount of total experience points (xp) that is needed to reach the supplied level.<br/>
    */
   private long xpToReach(int level) {
      return round(
         pow(
            (level * getXpConst(level)), 2
         ) - pow(
            getXpConst(level), 2
         )
      );
   } // END xpToReach
} // END class DefaultExperienceable
